package com.zxhhyj.storm.database

import com.zxhhyj.storm.common.SQL
import com.zxhhyj.storm.common.unpacking
import com.zxhhyj.storm.database.sqlite.SQLiteConnection
import com.zxhhyj.storm.dsl.AssignmentsBuilder
import com.zxhhyj.storm.dsl.FormBuilder
import com.zxhhyj.storm.schema.Table

class Database(private val sqliteConnection: SQLiteConnection) {

    fun createTable(table: Table<*>, ifNotExists: Boolean = false) {
        sqliteConnection.execSQL(SQL.createTableStr(table, ifNotExists))
    }

    fun prepare(sql: String) = sqliteConnection.prepare(sql)

    fun beginTransaction() {
        sqliteConnection.execSQL("BEGIN IMMEDIATE TRANSACTION")
    }

    fun setTransactionSuccessful() {
        sqliteConnection.execSQL("END TRANSACTION")
    }

    fun endTransaction() {
        sqliteConnection.execSQL("ROLLBACK TRANSACTION")
    }

    fun beginTransaction(block: Database.() -> Unit) {
        beginTransaction()
        try {
            block()
            setTransactionSuccessful()
        } catch (e: Throwable) {
            endTransaction()
            throw e
        }
    }

    private fun <T, E : Table<T>> insert(table: E, value: Map<String, *>) {
        val insertIntoSql = SQL.insertIntoStr(table, value)
        sqliteConnection.prepare(insertIntoSql)
    }

    fun <T, E : Table<T>> insert(table: E, block: E.(AssignmentsBuilder) -> Unit) {
        val assignmentsBuilder = AssignmentsBuilder()
        block(table, assignmentsBuilder)
        return insert(table, assignmentsBuilder.build())
    }

    fun <T> batchInsert(table: Table<T>, values: List<T>) {
        val batchInsertIntoSql = values
            .map { unpacking(table, it) }
            .let { SQL.batchInsertIntoStr(table, it) }
        sqliteConnection.execSQL(batchInsertIntoSql)
    }

    fun <T> insert(table: Table<T>, value: T) {
        val insertIntoSql = unpacking(table, value).let {
            SQL.insertIntoStr(table, it)
        }
        sqliteConnection.execSQL(insertIntoSql)
    }

    private fun <T, E : Table<T>> update(table: E, value: Map<String, *>) {
        val insertIntoSql = SQL.insertIntoStr(table, value)
        sqliteConnection.prepare(insertIntoSql)
    }

    fun <T, E : Table<T>> update(table: E, block: E.(AssignmentsBuilder) -> Unit) {
        val primaryKeyCount = table.columns.count { it.isPrimaryKey }

        when {
            primaryKeyCount == 0 -> throw IllegalStateException("表必须有一个主键列，但当前没有主键列")
            primaryKeyCount > 1 -> throw IllegalStateException("表只能有一个主键列，但当前有 $primaryKeyCount 个主键列")
        }

        val assignmentsBuilder = AssignmentsBuilder()
        block(table, assignmentsBuilder)
        return update(table, assignmentsBuilder.build())
    }

    fun <T, E : Table<T>> form(table: E): FormBuilder<T, E> {
        return FormBuilder(this, table)
    }
}